package com.sayice.robotconflict.game.object;

import com.sayice.robotconflict.common.util.MathUtil;
import com.sayice.robotconflict.common.util.SpringContextUtil;
import com.sayice.robotconflict.game.Game;
import com.sayice.robotconflict.game.msg.Message;
import com.sayice.robotconflict.game.msg.data.send.*;
import com.sayice.robotconflict.game.object.Player.State;
import com.sayice.robotconflict.game.object.property.Coord;
import com.sayice.robotconflict.service.PlayerService;

import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

/**
 * 游戏房间
 *
 * @author shubing
 */
public class GameRoom implements Runnable {
    /**
     * 对局线程池
     */
    private static final ThreadPoolExecutor GAME_EXECUTOR = new ThreadPoolExecutor(100, 3000, 1, TimeUnit.DAYS,
            new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    
    private static final PlayerService PLAYER_SERVICE = SpringContextUtil.getBean(PlayerService.class);
    
    // TODO 删掉
    static {
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("GAME_EXECUTOR 活跃线程数: " + GAME_EXECUTOR.getActiveCount());
                System.out.println("房间Map: " + Game.GAME_ROOM_MAP);
                System.out.println("在线玩家: " + Game.PLAYER_GROUP);
            }
        }).start();
    }
    
    /**
     * 房间号
     */
    private final int id;
    
    /**
     * 队伍1
     */
    private final Team team1;
    
    /**
     * 队伍2
     */
    private final Team team2;
    
    /* ---------------- 用于判断对局是否结束的相关属性 -------------- */
    
    /**
     * 获胜队伍
     */
    public final AtomicReference<Team> winner = new AtomicReference<>();
    
    /**
     * 在线玩家数量
     */
    public final AtomicInteger onlineCount = new AtomicInteger();
    
    /**
     * 对局时长
     */
    private int gameTime;
    
    private GameRoom(int roomId, Team team1, Team team2) {
        this.id = roomId;
        this.team1 = team1;
        this.team2 = team2;
        this.onlineCount.set(team1.size() + team2.size());
        team1.gameInit(this, 0);
        team2.gameInit(this, 1);
    }
    
    /**
     * 创建对局
     *
     * @param team1 队伍1
     * @param team2 队伍2
     * @return 是否创建成功
     */
    public static boolean createGame(Team team1, Team team2) {
        int roomId = Game.MAX_GAME_ROOM_ID.incrementAndGet();
        GameRoom room = new GameRoom(roomId, team1, team2);
        try {
            GAME_EXECUTOR.execute(room);
            Game.GAME_ROOM_MAP.put(roomId, room);
            return true;
        } catch (RejectedExecutionException e) {
            return false;
        }
    }
    
    /**
     * 获取敌方队伍
     */
    public Team rivalOf(Team team) {
        if (team == team1) {
            return team2;
        } else if (team == team2) {
            return team1;
        } else {
            return null;
        }
    }
    
    @Override
    public void run() {
        sendToTeams(new Message("game", "surrenderedCount", 0));
        team1.send(new Message("game_info", null, gameInfo(team1)));
        team2.send(new Message("game_info", null, gameInfo(team2)));
        while (true) {
            Team winnerTeam = winner.get();
            if (winnerTeam != null) {
                close(winnerTeam);
                return;
            }
            if (onlineCount.get() <= 0 || (team1.robots.isEmpty() && team2.robots.isEmpty())) {
                close(null);
                return;
            }
            if ((gameTime += Game.TICK_TIME) >= Game.MAX_GAME_TIME) {
                close(null);
                return;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(Game.TICK_TIME - 2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            tickAction();
            sendToTeams(new Message("frame_data", null, frameData()));
        }
    }
    
    /**
     * 获取对局信息
     *
     * @param team 队伍
     * @return 队伍下标、玩家列表等对局信息
     */
    public GameInfo gameInfo(Team team) {
        int teamIndex;
        if (team == team1) {
            teamIndex = 0;
        } else if (team == team2) {
            teamIndex = 1;
        } else {
            return null;
        }
        return new GameInfo(teamIndex, new Coord[]{team1.towerCoord, team2.towerCoord},
                new GamePlayerInfo[][]{team1.playerInfo(), team2.playerInfo()});
    }
    
    /**
     * 获取帧数据
     *
     * @return 当前时刻的对象状态信息
     */
    private GameFrameData frameData() {
        return new GameFrameData(gameTime, new GameTowerInfo[]{team1.towerInfo(), team2.towerInfo()},
                new GameRobotInfo[][]{team1.robotInfo(), team2.robotInfo()});
    }
    
    /**
     * 执行一个 Tick 的操作
     */
    private void tickAction() {
        team1.tickAction();
        team2.tickAction();
        Stream.concat(team1.robots.stream(), team2.robots.stream()).forEach(Robot::tickAction);
        // 机器人之间的碰撞检测
        while (true) {
            Optional<Robot> collidedRobot = Stream.concat(team1.robots.stream(), team2.robots.stream()).filter(robot1 -> {
                Optional<Robot> optionalRobot2 = Stream.concat(team1.robots.stream(), team2.robots.stream())
                        .filter(robot2 -> robot2 != robot1 && robot1.collidedWith(robot2.coord.getX(), robot2.coord.getY(),
                                robot2.coord.getX() + Game.ROBOT_SIZE, robot2.coord.getY() + Game.ROBOT_SIZE))
                        .findFirst();
                if (!optionalRobot2.isPresent()) {
                    return false;
                }
                Robot robot2 = optionalRobot2.get();
                double diffX = robot2.coord.getX() - robot1.coord.getX();
                double diffY = robot2.coord.getY() - robot1.coord.getY();
                // 若为主动碰撞，则不移动，回退坐标
                if (robot1.speed != 0 && (MathUtil.isSameSign(robot1.coord.offsetX(), diffX)
                        || MathUtil.isSameSign(robot1.coord.offsetY(), diffY))) {
                    robot1.coord.rewind(null, null);
                    robot1.speed = 0;
                    robot1.setCollidedRobot(robot2);
                }
                if (robot2.speed != 0 && (!MathUtil.isSameSign(robot2.coord.offsetX(), diffX)
                        || !MathUtil.isSameSign(robot2.coord.offsetY(), diffY))) {
                    robot2.coord.rewind(null, null);
                    robot2.speed = 0;
                    robot2.setCollidedRobot(robot1);
                }
                return true;
            }).findFirst();
            if (!collidedRobot.isPresent()) {
                break;
            }
        }
    }
    
    /**
     * 向所有队伍发送消息
     *
     * @param msg 消息
     */
    private void sendToTeams(Message msg) {
        team1.send(msg);
        team2.send(msg);
    }
    
    /**
     * 关闭房间
     *
     * @param winner 获胜队伍，null表示平局
     */
    private void close(Team winner) {
        Game.GAME_ROOM_MAP.remove(id);
        // 回收房间号
        int maxRoomId = Game.MAX_GAME_ROOM_ID.get();
        if (Game.GAME_ROOM_MAP.isEmpty()) {
            Game.MAX_GAME_ROOM_ID.compareAndSet(maxRoomId, 0);
        } else if (id == maxRoomId) {
            Game.MAX_GAME_ROOM_ID.compareAndSet(maxRoomId, maxRoomId - 1);
        }
        Stream.of(team1, team2).forEach(team -> {
            team.updateExpectedPlayersState(State.GAMING, State.IDLE, player -> {
                if (player.isVirtual) {
                    return;
                }
                player.reset();
                int newGameNum = player.gameNum + 1;
                int newWinRate;
                // 发送对局结束消息
                if (winner == null) {
                    player.send(new Message("action", "tie", null));
                    newWinRate = (int) Math.round(player.gameNum * player.winRate / (double) newGameNum);
                } else if (winner == team) {
                    player.send(new Message("action", "win", null));
                    newWinRate = (int) Math.round((player.gameNum * player.winRate + 100) / (double) newGameNum);
                } else {
                    player.send(new Message("action", "lose", null));
                    newWinRate = (int) Math.round(player.gameNum * player.winRate / (double) newGameNum);
                }
                // 更新对局次数和胜率
                PLAYER_SERVICE.updateGameNumAndWinRate(player.id, newGameNum, newWinRate);
                player.gameNum = newGameNum;
                player.winRate = newWinRate;
            });
        });
    }
}
